package cn.iocoder.yudao.framework.ip;

import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;

import java.io.IOException;

/**
 * IP 搜索工具类
 * <p>
 * ip2region.xdb 来源于 <a href="https://gitee.com/zhijiantianya/ip2region"/> 项目，
 * 可通过 <a href="https://gitee.com/woodyfar/xdb-maker"/> 项目生成。
 *
 * @author wanglhup
 */
@Slf4j
public class IpSearchUtils {
    private static final String IP_2_REGION_XDB = "ip2region.xdb";

    /**
     * IP 查询器，启动加载到内存中
     */
    private Searcher searcher;

    /**
     * 私有化构造
     */
    private IpSearchUtils() {
        try {
            long now = System.currentTimeMillis();
            byte[] bytes = ResourceUtil.readBytes(IP_2_REGION_XDB);
            searcher = Searcher.newWithBuffer(bytes);
            log.info("启动加载 IPUtils 成功，耗时 ({}) 毫秒", System.currentTimeMillis() - now);
        } catch (IOException e) {
            log.error("启动加载 IPUtils 失败", e);
        }
    }

    /**
     * 查询 IP 归属城市，如果不是归属中国，则只返回国家名称
     *
     * @param ip IP 地址，格式为 220.172.51.212
     * @return 城市名称，如：贵州贵阳、内网、美国、未知
     */
    public static String getCity(String ip) {
        return getCity(ip, true);
    }

    /**
     * 查询 IP 归属城市，如果不是归属中国，则只返回国家名称
     *
     * @param ip              IP 地址，格式为 220.172.51.212
     * @param includeProvince 是否包含省份
     * @return 城市名称，如：贵阳、内网、美国、未知
     */
    public static String getCity(String ip, boolean includeProvince) {
        return getCity(ip, includeProvince, "未知");
    }

    /**
     * 查询 IP 归属城市，如果不是归属中国，则只返回国家名称
     *
     * @param ip              IP 地址，格式为 220.172.51.212
     * @param includeProvince 是否包含省份
     * @param placeholder     未找到对应IP的归属地时的占位值，默认为“未知”
     * @return 城市名称，如：贵阳、内网、美国、未知
     */
    public static String getCity(String ip, boolean includeProvince, String placeholder) {
        String searched;
        try {
            long ipAddress = Searcher.checkIP(ip.trim());
            log.info("region {} as long {}", ip, ipAddress);
            searched = Holder.INSTANCE.searcher.search(ipAddress);
            log.info("region {} search succeed {}", ip, searched);
        } catch (Exception e) {
            log.error("region {} search failed", ip, e);
            return placeholder;
        }
        if (searched == null) {
            return placeholder;
        }
        if (searched.contains("内网")) {
            // 0|0|0|内网IP|内网IP
            return "内网";
        }
        String[] split = searched.split("\\|");
        String country = split[0];
        if (!country.equals("中国")) {
            // 美国|0|0|0|Level3
            return country;
        }
        // 中国|0|贵州省|贵阳市|电信
        // 中国|0|湖北省|襄阳|移动
        String province = split[2];
        if ("0".equals(province)) {
            province = "";
        } else {
            province = province.replaceAll("(省|自治区|市|特别行政区)$", "");
        }
        String city = split[3];
        if ("0".equals(city)) {
            city = province;
        } else {
            city = city.replaceAll("(市|地区)$", "");
        }
        if (StrUtil.isBlank(province) && StrUtil.isBlank(city)) {
            return placeholder;
        }
        return includeProvince && !province.equals(city) ? province + city : city;
    }

    private static class Holder {
        public final static IpSearchUtils INSTANCE = new IpSearchUtils();
    }
    
}
